Go 的 runtime 包
runtime 包是干什么的?
包运行时包含与 Go 的运行时系统交互的操作,例如控制 GoRoutine 的函数。说白了就是获取一些系统信息
GOMAXPROCS 设置核数
设置最大的可同时使用的 CPU 核数,通过 runtime.GOMAXPROCS
函数,应用程序何以在运行期间设置运行时系统中得 P 最大数量。但这会引起 “Stop the World”
。所以,应在应用程序最早的调用。并且最好是在运行 Go 程序之前设置好操作程序的环境变量 GOMAXPROCS,而不是在程序中调用 runtime.GOMAXPROCS
函数。无论我们传递给函数的整数值是什么值,运行时系统的P最大值总会在 1~256 之间。
同时可以利用 NumCPU 返回当前系统的 CPU 核数量
func init(){
//1.获取逻辑cpu的数量
fmt.Println("逻辑CPU的核数:",runtime.NumCPU())
//2.设置go程序执行的最大的:[1,256]
n := runtime.GOMAXPROCS(runtime.NumCPU())
fmt.Println(n)
}
Gosched 让出线程
让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行,这个函数的作用是让当前 goroutine 让出 CPU,当一个 goroutine 发生阻塞,Go 会自动地把与该 goroutine 处于同一系统线程的其他 goroutine 转移到另一个系统线程上去,以使这些 goroutine 不阻塞。
func main() {
go func() {
for i := 0; i < 5; i++ {
fmt.Println("goroutine....")
}
}()
for i := 0; i < 4; i++ {
//让出时间片,先让别的协议执行,它执行完,再回来执行此协程
runtime.Gosched()
fmt.Println("main....")
}
}
打印输出
goroutine....
goroutine....
goroutine....
goroutine....
goroutine....
main....
main....
main....
main....
Goexit 退出协程
退出当前 goroutine(但是 defer 语句会照常执行)
func main() {
//创建新建的协程
go func() {
fmt.Println("goroutine开始...")
//调用了别的函数
fun()
fmt.Println("goroutine结束...")
}() //别忘了()
//睡一会儿,不让主协程结束
time.Sleep(3 * time.Second)
}
func fun() {
defer fmt.Println("defer...")
//return //终止此函数
runtime.Goexit() //终止所在的协程
fmt.Println("fun函数....")
}
打印输出
goroutine开始...
defer...
NumGoroutine 协程数目
返回正在执行和排队的任务总数,runtime.NumGoroutine
函数在被调用后,会返回系统中的处于特定状态的 Goroutine 的数量。这里的特指是指 Grunnable\Gruning\Gsyscall\Gwaition
。处于这些状态的 Groutine 即被看做是活跃的或者说正在被调度。
注意:垃圾回收所在 Groutine 的状态也处于这个范围内的话,也会被纳入该计数器。
runtime.GC 主动 GC
会让运行时系统进行一次强制性的垃圾收集
- 强制的垃圾回收:不管怎样,都要进行的垃圾回收。
- 非强制的垃圾回收:只会在一定条件下进行的垃圾回收(即运行时,系统自上次垃圾回收之后新申请的堆内存的单元(也成为单元增量)达到指定的数值)。
取得 GOROOT 和 GOOS
获取 goroot 目录
func Pass() {
//获取 goroot 目录:
fmt.Println("GOROOT-->", runtime.GOROOT())
//获取操作系统
fmt.Println("os/platform-->", runtime.GOOS) // GOOS--> darwin,mac系统
}
Caller 返回函数栈信息
func Caller(skip int) (pc uintptr, file string, line int, ok bool)
参数:skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,.... 返回值:
- pc 是 uintptr 这个返回的是函数指针
- file 是函数所在文件名目录
- line 所在行号
- ok 是否可以获取到信息
使用例:
func main() {
for i := 0; i < 4; i++ {
test(i) // 13
}
}
func test(skip int) {
call(skip) // 18
}
func call(skip int) {
pc, file, line, ok := runtime.Caller(skip) // 22
pcName := runtime.FuncForPC(pc).Name() //获取函数名
fmt.Println(fmt.Sprintf("%v %s %d %t %s", pc, file, line, ok, pcName))
}
打印输出:
4717194 /home/alsritter/Desktop/co/test/sterror/main.go 22 true main.call
4717092 /home/alsritter/Desktop/co/test/sterror/main.go 18 true main.test
4717085 /home/alsritter/Desktop/co/test/sterror/main.go 13 true main.main
4399206 /data/home/alsritter/sdk/go1.17.3/src/runtime/proc.go 255 true runtime.main
这个 Caller 还有一个类似的函数
func Callers(skip int, pc []uintptr) int
这个方法传入一个 slice,Callers 方法会自动填满这个 slice,前面那个参数 skip 表示跳过几个栈方法
// 取出调用栈50个数据
pcSlice := make([]uintptr, 50)
count := runtime.Callers(2, pcSlice)
然后可以通过另一个方法根据取得的函数指针取得这些函数属性
// 取出调用栈50个数据
pcSlice := make([]uintptr, 50)
count := runtime.Callers(2, pcSlice)
pcSlice = pcSlice[:count] // 可能没有 50 个
// 返回的 *Frames 类似一个迭代器
frames := runtime.CallersFrames(pcSlice)
var frame runtime.Frame
more := count > 0
for more {
frame, more = frames.Next()
fmt.Println(fmt.Sprintf("%v %s %d %t %s", frame.PC, frame.File, frame.Line, frame.Function))
}